using System;
using System.Reflection;
using System.Windows.Forms;
using System.Xml.Schema;

namespace OpenComposite.Mapper
{
	public static class FillXmlSchemaTree
	{
		/// <summary>
		/// Fills a TreeView with a XmlSchema.
		/// </summary>
		/// <param name="tv">The TreeView to show the XmlSchema.</param>
		/// <param name="xsd">The XmlSchema to show.</param>
		public static void FillTree(TreeView tv, XmlSchema xsd)
		{
			tv.BeginUpdate();
			tv.Nodes.Clear();

			compileSchema(xsd);
			decodeSchema(xsd, null, tv);

			tv.ExpandAll();
			tv.EndUpdate();
		}

		#region CompileSchema
		private static void compileSchema(XmlSchema xsd)
		{
			if ( xsd == null )
				return;

			_errorMessage = "";
			try {
				XmlSchemaSet xset = new XmlSchemaSet();
				xset.ValidationEventHandler += new ValidationEventHandler(schemaValidationCallback);
				xset.Add(xsd);
				xset.Compile();

			} catch ( Exception ex ) {
				if ( !string.IsNullOrEmpty(_errorMessage) ) _errorMessage += Environment.NewLine;
				_errorMessage += ex.Message;
			}
			if ( !string.IsNullOrEmpty(_errorMessage) ) {
				string name = "";
				if ( xsd.Items.Count > 0 ) {
					foreach ( XmlSchemaObject xso in xsd.Items ) {
						XmlSchemaElement xse = xsd.Items[0] as XmlSchemaElement;
						if ( xse != null ) {
							name = xse.Name;
							break;
						}
					}
				}
				_errorMessage = string.Format("Errors in Business Object / XmlSchema{0}{1}{1}{2}",
					string.IsNullOrEmpty(name) ? "." : " \"" + name + "\".",
					Environment.NewLine, _errorMessage);

				throw new Exception(_errorMessage);
			}
		}

		private static string _errorMessage;
		private static void schemaValidationCallback(object sender, ValidationEventArgs e)
		{
			if ( !string.IsNullOrEmpty(_errorMessage) ) _errorMessage += Environment.NewLine;
			if ( e.Severity == XmlSeverityType.Error ) {
				_errorMessage += "Error: " + e.Message;
			}
		}
		#endregion

		#region DecodeSchema
		private static void decodeSchema(XmlSchema schema, TreeNode node, TreeView tv)
		{
			try {
				// decode schema
				decodeSchema2(schema, node, tv);
			} catch {
				throw;
			}
		}

		/// <summary>
		/// recursive decoder
		/// </summary>
		private static TreeNode decodeSchema2(XmlSchemaObject obj, TreeNode node, TreeView tv)
		{
			if ( obj == null )
				return node;

			TreeNode newNode = node;

			// convert the object to all types and then check what type actually exists
			XmlSchemaAnnotation annot = obj as XmlSchemaAnnotation;
			XmlSchemaAttribute attrib = obj as XmlSchemaAttribute;
			XmlSchemaFacet facet = obj as XmlSchemaFacet;
			XmlSchemaDocumentation doc = obj as XmlSchemaDocumentation;
			XmlSchemaAppInfo appInfo = obj as XmlSchemaAppInfo;
			XmlSchemaElement element = obj as XmlSchemaElement;
			XmlSchemaSimpleType simpleType = obj as XmlSchemaSimpleType;
			XmlSchemaComplexType complexType = obj as XmlSchemaComplexType;

			// if annotation, add a tree node and recurse for documentation and app info
			if ( annot != null ) {
				newNode = new TreeNode("--annotation--");
				newNode.Tag = new TreeNodeInfos(annot);
				//newNode.ImageIndex = 4;
				//newNode.SelectedImageIndex = 4;
				if ( node == null )
					tv.Nodes.Add(newNode);
				else
					node.Nodes.Add(newNode);
				foreach ( XmlSchemaObject schemaObject in annot.Items ) {
					decodeSchema2(schemaObject, newNode, tv);
				}
			} else
				// if attribute, add a tree node
				if ( attrib != null ) {
					newNode = new TreeNode(attrib.QualifiedName.Name);
					newNode.Tag = new TreeNodeInfos(attrib);
					//newNode.ImageIndex = 7;
					//newNode.SelectedImageIndex = 7;
					if ( node == null )
						tv.Nodes.Add(newNode);
					else
						node.Nodes.Add(newNode);
				} else
					// if facet, add a tree node
					if ( facet != null ) {
						newNode = new TreeNode(facet.ToString());
						newNode.Tag = new TreeNodeInfos(facet);
						//newNode.ImageIndex = 8;
						//newNode.SelectedImageIndex = 8;
						if ( node == null )
							tv.Nodes.Add(newNode);
						else
							node.Nodes.Add(newNode);
					} else
						// if documentation, add a tree node
						if ( doc != null ) {
							newNode = new TreeNode("--documentation--");
							newNode.Tag = new TreeNodeInfos(doc);
							//newNode.ImageIndex = 5;
							//newNode.SelectedImageIndex = 5;
							if ( node == null )
								tv.Nodes.Add(newNode);
							else
								node.Nodes.Add(newNode);
						} else
							// if app info, add a tree node
							if ( appInfo != null ) {
								newNode = new TreeNode("--app info--");
								newNode.Tag = new TreeNodeInfos(annot);
								//newNode.ImageIndex = 6;
								//newNode.SelectedImageIndex = 6;
								if ( node == null )
									tv.Nodes.Add(newNode);
								else
									node.Nodes.Add(newNode);
							} else
								// if an element, determine whether the element is a simple type or a complex type
								if ( element != null ) {
									XmlSchemaSimpleType st = element.SchemaType as XmlSchemaSimpleType;
									XmlSchemaComplexType ct = element.SchemaType as XmlSchemaComplexType;
									if ( st == null && ct == null ) {
										st = element.ElementSchemaType as XmlSchemaSimpleType;
										ct = element.ElementSchemaType as XmlSchemaComplexType;
									}

									if ( st != null ) {
										// this is a simple type element.  Recurse.
										TreeNode node2 = decodeSchema2(st, newNode, tv);
										node2.Text = element.Name;
									} else if ( ct != null ) {
										// this is a complex type element.  Recurse.
										TreeNode node2 = decodeSchema2(ct, newNode, tv);
										node2.Text = element.Name;
									} else {
										// This is a plain ol' fashioned element.
										newNode = new TreeNode(element.QualifiedName.Name);
										newNode.Tag = new TreeNodeInfos(element);
										//newNode.ImageIndex = 1;
										//newNode.SelectedImageIndex = 1;
										if ( node == null )
											tv.Nodes.Add(newNode);
										else
											node.Nodes.Add(newNode);
									}
								} else
									// if a simple type, then add a tree node and recurse facets
									if ( simpleType != null ) {
										newNode = new TreeNode(simpleType.QualifiedName.Name);
										newNode.Tag = new TreeNodeInfos(simpleType);
										//newNode.ImageIndex = 2;
										//newNode.SelectedImageIndex = 2;
										if ( node == null )
											tv.Nodes.Add(newNode);
										else
											node.Nodes.Add(newNode);
										XmlSchemaSimpleTypeRestriction rest = simpleType.Content as XmlSchemaSimpleTypeRestriction;
										if ( rest != null ) {
											foreach ( XmlSchemaFacet schemaObject in rest.Facets ) {
												decodeSchema2(schemaObject, newNode, tv);
											}
										}
									} else
										// if a complex type, add a tree node and recurse its sequence
										if ( complexType != null ) {
											newNode = new TreeNode(complexType.Name);
											newNode.Tag = new TreeNodeInfos(complexType);
											//newNode.ImageIndex = 3;
											//newNode.SelectedImageIndex = 3;
											if ( node == null )
												tv.Nodes.Add(newNode);
											else
												node.Nodes.Add(newNode);

											XmlSchemaSequence seq = complexType.Particle as XmlSchemaSequence;
											XmlSchemaChoice choice = complexType.Particle as XmlSchemaChoice;
											if ( seq != null ) {
												foreach ( XmlSchemaObject schemaObject in seq.Items ) {
													decodeSchema2(schemaObject, newNode, tv);
												}
											} else if ( choice != null ) {
												foreach ( XmlSchemaObject schemaObj in choice.Items ) {
													decodeSchema2(schemaObj, newNode, tv);
												}
											}
										}

			// now recurse any object collection of the type.
			foreach ( PropertyInfo property in obj.GetType().GetProperties() ) {
				if ( property.PropertyType.FullName == "System.Xml.Schema.XmlSchemaObjectCollection" ) {
					XmlSchemaObjectCollection childObjectCollection = (XmlSchemaObjectCollection)property.GetValue(obj, null);
					foreach ( XmlSchemaObject schemaObject in childObjectCollection ) {
						if ( obj is XmlSchema ) {
							if ( schemaObject is XmlSchemaElement )
								decodeSchema2(schemaObject, newNode, tv);
						} else {
							decodeSchema2(schemaObject, newNode, tv);
						}
					}
				}
			}
			return newNode;
		}
		#endregion
	}
}
